package com.duojuhe.aspect;


import com.duojuhe.common.annotation.RateLimiter;
import com.duojuhe.common.constant.SingleStringConstant;
import com.duojuhe.common.constant.SystemConstants;
import com.duojuhe.common.enums.RateLimiterType;
import com.duojuhe.common.exception.base.DuoJuHeException;
import com.duojuhe.common.result.ErrorCodes;
import com.duojuhe.common.utils.iputil.IPUtils;
import com.duojuhe.common.utils.servletutils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

/**
 * 限流处理
 *
 * @author echo
 */
@Slf4j
@Aspect
@Component
public class RateLimiterControllerAspect {
    private RedisTemplate<Object, Object> redisTemplate;

    private RedisScript<Long> limitScript;

    @Resource
    public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate){
        this.redisTemplate = redisTemplate;
    }

    @Resource
    public void setLimitScript(RedisScript<Long> limitScript){
        this.limitScript = limitScript;
    }

    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable{
        //限流时间,单位秒
        int limitTime = rateLimiter.limitTime();
        //限流次数
        int limitCount = rateLimiter.limitCount();
        //限流key
        String combineKey = getCombineKey(point,rateLimiter);
        List<Object> keys = Collections.singletonList(combineKey);
        try{
            Long number = redisTemplate.execute(limitScript, keys, limitCount, limitTime);
            if (limitCount!=0&&number!=null && number.intValue() > limitCount){
                log.info("限制请求'{}',当前请求'{}',缓存key'{}'", limitCount, number.intValue(), combineKey);
                throw new DuoJuHeException(ErrorCodes.REQUEST_TOO_FREQUENTLY_ERROR);
            }
        }catch (DuoJuHeException e) {
            log.info("【请求限流】限制请求出现自定义异常", e);
            throw e;
        }catch (Exception e){
            log.info("【请求限流】限制请求出现系统异常", e);
            throw new DuoJuHeException(ErrorCodes.REQUEST_TOO_FREQUENTLY_ERROR.getMessage());
        }
    }

    /**
     * 获取限流key
     * @param point
     * @param rateLimiter
     * @return
     */
    private String getCombineKey(JoinPoint point,RateLimiter rateLimiter){
        //水平横线分隔符
        String horizontal_line = SingleStringConstant.HORIZONTAL_LINE;
        //key对象
        StringBuilder stringBuffer = new StringBuilder(SystemConstants.RATE_LIMIT_KEY);
        if (rateLimiter.limitType() == RateLimiterType.IP){
            stringBuffer.append(IPUtils.getClientIP(ServletUtils.getRequest())).append(horizontal_line);
        }
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        stringBuffer.append(targetClass.getName()).append(horizontal_line).append(method.getName());
        return stringBuffer.toString();
    }
}
